/*
 * Copyright (C) 2010-2011 Graz University of Technology, Austria. All Rights reserved. 
 *
 * Contact: Simone Kopeinik <simone.kopeinik@tugraz.at>
 * 	   Graz University of Technology, Knowledge Management Institute, Brückenkopfgasse 1/6, 8020 Graz 	
 * 	   <http://www.kmi.tugraz.at/>
 * 
 * This software is part of the TARGET platform developed by the
 * TARGET EC-funded project www.reachyourtarget.org
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * The GNU General Public License is available at <http://www.gnu.org/licenses/>.
 *
 */
package at.tugraz.kmi.css.cbkst2.prereq;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;

import at.tugraz.kmi.css.cbkst2.CbKSTCompetence;
import at.tugraz.kmi.css.cbkst2.ImmutablePrerequesiteOrderSet;
import at.tugraz.kmi.css.cbkst2.PERSISTENCE_TYPE;
import at.tugraz.kmi.css.cbkst2.PrerequesiteOrderSet;
import at.tugraz.kmi.css.cbkst2.PrerequesiteRelation;
import at.tugraz.kmi.css.cbkst2.StructureLoader;
import at.tugraz.kmi.css.cbkst2.prereq.structurevis.FileFormatError;
import at.tugraz.kmi.css.commons.Bean;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

import edu.uci.ics.jung.graph.ObservableGraph;
import edu.uci.ics.jung.graph.event.GraphEvent;
import edu.uci.ics.jung.graph.event.GraphEventListener;

/**
 * The {@linkplain CompetenceManager} loads the conceptual model of a CbKST
 * system. The model describes the various entities involved in the system and
 * their relationships.
 * 
 * @author Georg Öttl <georg.oettl@gmail.com>
 */
@Singleton
public final class CompetenceManager extends Bean implements Serializable {

	/** Property name JavaBean. */
	public static final String																			PROPERTY_PREREQUESITE_GRAPH			= "prerequesiteGraph";

	/** Property name JavaBean. */
	public static final String																			PROPERTY_TRANSITIVE_CLOSURE_GRAPH	= "transitiveClosureGraph";

	private static final long																			serialVersionUID					= 1L;

	private final Map<PERSISTENCE_TYPE, StructureLoader<CbKSTCompetence>>								extensions;

	private final Set<StructureLoader<CbKSTCompetence>>													loaders;

	private PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>							prerequesiteGraph;

	private final transient Provider<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>>	skillStructureProvider;

	/**
	 * @param loaders
	 *            A set of loaders that can load a CbKST Structure.
	 */
	// START SNIPPET: CM-IO
	@Inject
	CompetenceManager(
			Set<StructureLoader<CbKSTCompetence>> loaders,
			Provider<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>> skillStructureProvider) {
		this.loaders = loaders;
		this.skillStructureProvider = skillStructureProvider;
		setPrerequesiteGraph(skillStructureProvider.get());
		extensions = new HashMap<PERSISTENCE_TYPE, StructureLoader<CbKSTCompetence>>();
		for (StructureLoader<CbKSTCompetence> structureLoader : loaders) {
			extensions.put(structureLoader.type(), structureLoader);
		}
	}

	// END SNIPPET: CM-IO

	// ------------------ Accessors

	/**
	 * Add all competences to the internal state
	 * 
	 * @param competences
	 *            the competences to add.
	 */
	public void addAll(Collection<CbKSTCompetence> competences) {
		if (competences.size() > 0) {
			for (CbKSTCompetence cbKSTCompetence : competences) {
				prerequesiteGraph.addVertex(cbKSTCompetence);
			}
			firePropertyChange(PROPERTY_PREREQUESITE_GRAPH, null,
					prerequesiteGraph);
		}
	}

	/**
	 * 
	 * @return A list of available loaders for structure <code>S</code>
	 */
	@Nonnull
	public List<PERSISTENCE_TYPE> getLoaderTypes() {
		ArrayList<PERSISTENCE_TYPE> loaderTypes = new ArrayList<PERSISTENCE_TYPE>();
		for (StructureLoader<CbKSTCompetence> structureLoader : loaders) {
			loaderTypes.add(structureLoader.type());
		}
		return loaderTypes;
	}

	public PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> getPrerequesiteGraph() {
		return prerequesiteGraph;
	}

	public ObservableGraph<CbKSTCompetence, PrerequesiteRelation> getTransitiveClosureGraph() {
		return prerequesiteGraph.getTransitiveClosureGraph();
	}

	public void setTransitiveClosureGraph(
			ObservableGraph<CbKSTCompetence, PrerequesiteRelation> graph) {
		// compatibility only...
		// do nothing.
	}

	/**
	 * 
	 * @see CompetenceManager#PROPERTY_PREREQUESITE_GRAPH
	 * @return The skill structure
	 * 
	 */
	public ImmutablePrerequesiteOrderSet<CbKSTCompetence> getStructure() {
		return new PrerequesiteOrderSetAdapter(prerequesiteGraph);
	}

	/**
	 * 
	 * 
	 * @param in
	 *            A inputstream to load the set from
	 * @param loader
	 *            the loader to be used for loading
	 * @return an immutable set of the internal state.
	 */
	public ImmutablePrerequesiteOrderSet<CbKSTCompetence> loadStructure(
			InputStream in, PERSISTENCE_TYPE loader) {
		Reader inputData;
		try {
			inputData = new InputStreamReader(in);
			StructureLoader<CbKSTCompetence> structureLoader = extensions
					.get(loader);
			PrerequesiteOrderSet<CbKSTCompetence> graph = structureLoader
					.readStructure(inputData);
			setStructure(graph);
			return getStructure();
		} catch (FileNotFoundException e) {
			throw new IllegalStateException(e);
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}

	/**
	 * @param f
	 *            The file to load the set from
	 * @return The loaded ordered set.
	 * @throws FileFormatError
	 *             If file format error (e.g. wrong loader used)
	 * @throws FileNotFoundException
	 *             If file was not found.
	 */
	public ImmutablePrerequesiteOrderSet<CbKSTCompetence> loadStructureFromExtension(
			File f) throws FileFormatError, FileNotFoundException {
		ImmutablePrerequesiteOrderSet<CbKSTCompetence> loadStructure = loadStructure(
				new FileInputStream(f), getPersistenceTypeFromExtension(f));
		setStructure(loadStructure);
		return loadStructure;
	}

	/**
	 * 
	 * Creates new Structure
	 * 
	 */
	public void newStructure() {
		setPrerequesiteGraph(skillStructureProvider.get());
	}

	/**
	 * Saves the structure to a file.
	 * 
	 * 
	 * @param f
	 *            The file to save to
	 * @param loader
	 *            the loader to use
	 * @return the file reference
	 */
	public File saveStructure(File f, PERSISTENCE_TYPE loader) {
		StructureLoader<CbKSTCompetence> structureLoader = extensions
				.get(loader);
		ImmutablePrerequesiteOrderSet<CbKSTCompetence> structure = getStructure();
		FileWriter data;
		try {
			if (!f.exists()) {
				f.createNewFile();
			} else {
				f.delete();
				f.createNewFile();
			}
			data = new FileWriter(f);
			structureLoader.writeStructure(structure, data);
			data.close();
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
		return f;
	}

	public final static String	PROPERTY_CbKST_VERTICES	= "cbKSTVertices";

	public Set<CbKSTCompetence> getCbKSTVertices() {
		return new HashSet<CbKSTCompetence>(
				prerequesiteGraph.getCbKSTVertices());
	}

	/**
	 * Sets the proveded cbkst competences. Replaces all old / the graph.
	 * 
	 * @param vertices
	 */
	public void setCbKSTVertices(Set<CbKSTCompetence> vertices) {
		synchronized (prerequesiteGraph) {
			PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> s = skillStructureProvider
					.get();
			for (CbKSTCompetence cbKSTVertex : vertices) {
				s.addVertex(cbKSTVertex);
			}
			setPrerequesiteGraph(s);
		}
	}

	/**
	 * Saves the structure to a file. Guesses the loader to use by the extension
	 * of the file provided.
	 * 
	 * @param f
	 *            The file to save to
	 * @return The file reference
	 * @throws FileFormatError
	 *             Upon format errors
	 */
	public File saveStructureFromExtension(File f) throws FileFormatError {
		return saveStructure(f, getPersistenceTypeFromExtension(f));
	}

	/**
	 * 
	 * @param prerequesiteGraph
	 *            the new prerequesite graph to use
	 */
	public void setPrerequesiteGraph(
			final PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> prerequesiteGraph) {
		final PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> old = this.prerequesiteGraph;
		this.prerequesiteGraph = prerequesiteGraph;
		this.prerequesiteGraph
				.addGraphEventListener(new GraphEventListener<CbKSTCompetence, PrerequesiteRelation>() {

					@Override
					public void handleGraphEvent(
							GraphEvent<CbKSTCompetence, PrerequesiteRelation> evt) {
						firePropertyChange(PROPERTY_CbKST_VERTICES, old,
								getCbKSTVertices());
					}
				});

		firePropertyChange(PROPERTY_PREREQUESITE_GRAPH, old,
				this.prerequesiteGraph);
		firePropertyChange(PROPERTY_TRANSITIVE_CLOSURE_GRAPH, old,
				this.prerequesiteGraph.getTransitiveClosureGraph());
		firePropertyChange(PROPERTY_CbKST_VERTICES, old, getCbKSTVertices());
	}

	/**
	 * 
	 * @see CompetenceManager#PROPERTY_PREREQUESITE_GRAPH
	 * 
	 * @param prerequesiteGraph
	 *            The skill structure to set.
	 */
	void setStructure(
			@Nonnull ImmutablePrerequesiteOrderSet<CbKSTCompetence> skillStructure) {
		PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> ns = skillStructureProvider
				.get();
		ns.addAll(skillStructure);
		PrerequesiteOrderSet<CbKSTCompetence> oldVal = prerequesiteGraph;
		prerequesiteGraph = ns;
		firePropertyChange(PROPERTY_PREREQUESITE_GRAPH, oldVal,
				prerequesiteGraph);
	}

	private PERSISTENCE_TYPE getPersistenceTypeFromExtension(File f)
			throws FileFormatError {
		if (f == null) {
			throw new FileFormatError(
					"cannot guess file extension from null value!");
		}
		String filename = f.getName();
		String ext = (filename.lastIndexOf('.') == -1) ? "" : filename
				.substring(filename.lastIndexOf('.') + 1, filename.length());
		if (ext.length() == 0) {
			throw new FileFormatError(
					"Unknown format, could not guess format from file extension!");
		}
		ext = ext.toLowerCase();
		PERSISTENCE_TYPE persistenceTYPE = PERSISTENCE_TYPE.valueOf(ext);
		return persistenceTYPE;
	}
}
